﻿using System;
using System.Collections.Generic;
using Microsoft.Practices.PageFlow;
using System.Collections;

namespace PageFlow.Util
{
    

    public class PageFlowUtil : IPageFlowUtil
    {
        private const string UDKEY_RETURN_TO = "returnto";

        private const string UDKEY_EXTERNAL_PAGE_FLOW = "externalPageFlow";
        /// <summary>
        /// Start method to use when the pageflow should be able to return to the
        /// pageFlow which initialized it
        /// </summary>
        /// <param name="pageFlowToStart"></param>
        /// <param name="pageFlowToReturnTo"></param>
        public void Start(IPageFlow pageFlowToStart, IPageFlow pageFlowToReturnTo)
        {
            Start(pageFlowToStart);
            pageFlowToStart.UserData[UDKEY_RETURN_TO] = pageFlowToReturnTo;
        }

        /// <summary>
        /// The start method clears all other running page flows and
        /// makes sure the page flow is in initial state
        /// </summary>
        /// <param name="pageFlow"></param>
        public void Start(IPageFlow pageFlow)
        {
            Start(pageFlow, true);
        }

        /// <summary>
        /// Starts a page flow
        /// </summary>
        /// <param name="pageFlow">The page flow to start</param>
        /// <param name="clearUserData">If to clear all the user data</param>
        public void Start(IPageFlow pageFlow, bool clearUserData)
        {
            //Aborting the currently running page flow as the 
            //start method ensures the start page to be the initial page
            if (clearUserData)
            {
                ClearUserData(pageFlow);
            }

            EnsureStartPage(pageFlow);

            //Retstarts the page flow, with no user data and 
            //on the defined pageflow start page
            pageFlow.Start();
        }

        public void ClearUserData(IPageFlow pageFlow)
        {
            ClearUserData(pageFlow, false);
        }

        /// <summary>
        /// Clears all user data in the page flow.
        /// </summary>
        /// <param name="pageFlow">The page flow from where to clear the user data</param>
        /// <param name="keepExternalWorkflowReference">if the pageFlow contains a reference to 
        /// an external pageflow, that key is not cleared</param>
        public void ClearUserData(IPageFlow pageFlow, bool keepExternalWorkflowReference)
        {
            ArrayList keys = new ArrayList(pageFlow.UserData.Keys);

            for (int i = 0; i < keys.Count; i++)
            {
                string key = (string)keys[i];

                if (!key.StartsWith("__$") && !(keepExternalWorkflowReference && key.Equals(UDKEY_EXTERNAL_PAGE_FLOW)))
                {
                    pageFlow.UserData.Remove(key);
                }
            }
        }

        public void Complete(IPageFlow pageFlow)
        {
            if (IsRunning(pageFlow))
            {
                pageFlow.Complete();
            }
        }

        public void Navigate(IPageFlow pageFlow, string transition)
        {
            Navigate(pageFlow, transition, new string[0]);
        }

        public void Abort(IPageFlow pageFlow)
        {
            if (!IsRunning(pageFlow))
            {
                Start(pageFlow);
            }
            pageFlow.Abort(true);
        }

        public void RemoveUserDataKey(IPageFlow pageFlow, string key)
        {
            if (pageFlow != null && IsRunning(pageFlow))
            {
                if (pageFlow.UserData.Contains(key))
                {
                    pageFlow.UserData.Remove(key);
                }
            }
        }

        public T GetUserData<T>(IPageFlow pageFlow, string key, bool abortOnMissing)
        {
            if (!IsRunning(pageFlow))
            {
                return default(T);
            }
            if (!pageFlow.UserData.Contains(key))
            {
                if (abortOnMissing)
                {
                    Abort(pageFlow);
                }
                return default(T);
            }
            return (T)pageFlow.UserData[key];
        }

        public T GetUserData<T>(IPageFlow pageFlow, string key)
        {
            return GetUserData<T>(pageFlow, key, false);
        }

        public void Navigate(IPageFlow pageFlow, string transition, string[] keys)
        {
            if (!IsRunning(pageFlow))
            {
                // not running
                Abort(pageFlow);
                return;
            }
            if (!pageFlow.CurrentPage.Transitions.Contains(transition))
            {
                // transition does not exist
                Abort(pageFlow);
                return;
            }
            if (keys.Length > 0)
            {
                foreach (string key in keys)
                {
                    if (!pageFlow.UserData.Contains(key))
                    {
                        // missing user data
                        Abort(pageFlow);
                        return;
                    }
                }
            }
            pageFlow.Navigate(transition);
        }

        public void EnsureStartPage(IPageFlow pageFlow)
        {
            EnsureCurrentPage(pageFlow, pageFlow.Definition.StartPage.Name);
        }

        public void EnsureCurrentPage(IPageFlow pageFlow, string page)
        {
            if (!IsRunning(pageFlow))
            {
                pageFlow.Start();
            }
            if (!IsCurrentPage(pageFlow, page))
            {
                pageFlow.SetCurrentPage(page);
            }
        }

        /// <summary>
        /// Ensures that the given pageflow has the given data in its userdata and that the data parts are of the 
        /// type given in the dictionary. If the data is not found, it is of the wrong type or if the pageflow is
        /// simply not running the pageflow is redirected to the onProblemPage.
        /// </summary>
        /// <param name="pageFlow">The pageflow to ensure data integrity for</param>
        /// <param name="dataNeeded">The data that must be available. The values in the list are keys that should
        /// be found in the pageflow userdata.</param>
        /// <param name="onProblemPage">The page to redirect to in case of a problem.</param>
        public void EnsureDataIntegrity(IPageFlow pageFlow, List<string> dataNeeded, string onProblemPage)
        {
            IDictionary<string, Type> data = new Dictionary<string, Type>();
            dataNeeded.ForEach(delegate(string key) { data.Add(key, null); });
            EnsureDataIntegrity(pageFlow, data, onProblemPage);
        }

        public void EnsureDataIntegrity(IPageFlow pageFlow, IDictionary<string, Type> dataNeeded)
        {
            EnsureDataIntegrity(pageFlow, dataNeeded, String.Empty);
        }

        /// <summary>
        /// Ensures that the given pageflow has the given data in its userdata and that the data parts are of the 
        /// type given in the dictionary. If the data is not found, it is of the wrong type or if the pageflow is
        /// simply not running the pageflow is redirected to the onProblemPage.
        /// </summary>
        /// <param name="pageFlow">The pageflow to ensure data integrity for</param>
        /// <param name="dataNeeded">The data that must be available. The key is a key that should be found in the
        /// pageflow userdata. The value is either a Type or null if there is no need to check the type.</param>
        /// <param name="onProblemPage">The page to redirect to in case of a problem.</param>
        public void EnsureDataIntegrity(IPageFlow pageFlow, IDictionary<string, Type> dataNeeded, string onProblemPage)
        {
            if (!IsRunning(pageFlow))
            {
                Start(pageFlow);
            }

            foreach (KeyValuePair<string, Type> valuePair in dataNeeded)
            {
                if (!pageFlow.UserData.Contains(valuePair.Key))
                {
                    Abort(pageFlow);
                    return;
                }
                if ((valuePair.Value == null) || (pageFlow.UserData[valuePair.Key].GetType().Equals(valuePair.Value)))
                    continue;
                Abort(pageFlow);
                return;
            }
        }

        public bool IsCurrentPage(IPageFlow pageFlow, string page)
        {
            if (!IsRunning(pageFlow))
            {
                return false;
            }
            return page.Equals(pageFlow.CurrentPage.Name);
        }

        public bool IsRunning(IPageFlow pageFlow)
        {
            return IsRunning(pageFlow, false);
        }

        public bool IsRunning(IPageFlow pageFlow, bool start)
        {
            if (pageFlow != null)
            {
                if (pageFlow.Status != PageFlowStatus.Running)
                {
                    if (start)
                    {
                        pageFlow.Start();
                        return (pageFlow.Status == PageFlowStatus.Running);
                    }
                    return false;
                }
                return true;
            }
            return false;
        }

        /// <summary>
        /// Redirects to an external pageflow by suspending the fromPageflow and starting the toPageflow. The external
        /// userdata will be added to the toPageflow userdata.
        /// </summary>
        /// <param name="fromPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="toPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="state">The state where the toPageflow should be set before redirecting</param>
        public void RedirectToExternalPageFlow(IPageFlow fromPageflow, IPageFlow toPageflow, string state)
        {
            RedirectToExternalPageFlow(fromPageflow, toPageflow, state, null);
        }

        /// <summary>
        /// Redirects to an external pageflow by suspending the fromPageflow and starting the toPageflow. The external
        /// userdata will be added to the toPageflow userdata.
        /// </summary>
        /// <param name="fromPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="toPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="state">The state where the toPageflow should be set before redirecting</param>
        /// <param name="externalUserData">The data needed by the toPageFlow</param>
        public void RedirectToExternalPageFlow(IPageFlow fromPageflow, IPageFlow toPageflow, string state,
                                                      Dictionary<string, object> externalUserData)
        {
            fromPageflow.Suspend();
            toPageflow.Start();
            if (externalUserData != null)
            {
                foreach (KeyValuePair<string, object> keyValuePair in externalUserData)
                {
                    toPageflow.UserData[keyValuePair.Key] = keyValuePair.Value;
                }
            }
            toPageflow.UserData[UDKEY_EXTERNAL_PAGE_FLOW] = fromPageflow.Id;

            if (!string.IsNullOrEmpty(state))
            {
                toPageflow.SetCurrentPage(state);
            }

            toPageflow.RedirectToCurrentPage();
        }

        /// <summary>
        /// Returns if the pageFlow (currentPageFlow) is a child of a parent workflow, which
        /// it can/should return to if itself is cancelled or completed.        
        /// </summary>
        /// <param name="currentPageFlow"></param>
        /// <returns></returns>
        public bool IsExternalWorkFlow(IPageFlow currentPageFlow)
        {
            if (currentPageFlow != null && currentPageFlow.UserData.Contains(UDKEY_EXTERNAL_PAGE_FLOW))
            {
                return currentPageFlow.UserData[UDKEY_EXTERNAL_PAGE_FLOW] != null;
            }
            return false;
        }


        /// <summary>
        /// Redirects to an external pageflow by suspending the fromPageflow and starting the toPageflow. The external
        /// userdata will be added to the toPageflow userdata.
        /// </summary>
        /// <param name="fromPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="toPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="externalUserData">The data needed by the toPageFlow</param>
        public void RedirectToExternalPageFlow(IPageFlow fromPageflow, IPageFlow toPageflow,
                                                      Dictionary<string, object> externalUserData)
        {
            RedirectToExternalPageFlow(fromPageflow, toPageflow, string.Empty, externalUserData);
        }

        /// <summary>
        /// Redirects to an external pageflow by suspending the fromPageflow and starting the toPageflow. The external
        /// userdata will be added to the toPageflow userdata.
        /// </summary>
        /// <param name="fromPageflow">the pageflow being suspended while toPageflow is running.</param>
        /// <param name="toPageflow">the pageflow being suspended while toPageflow is running.</param>
        public void RedirectToExternalPageFlow(IPageFlow fromPageflow, IPageFlow toPageflow)
        {
            RedirectToExternalPageFlow(fromPageflow, toPageflow, string.Empty, null);
        }

        /// <summary>
        /// Redirects "back" to a pageflow from which was redirected. If RedirectToExternalPageFlow(IPageFlow,IPageFlow,Dictionary) was
        /// used to redirect to the given pageFlow this is a method to use to redirect back to that pageflow.
        /// </summary>
        /// <param name="pageFlow">The pageflow to which was redirected using RedirectToExternalPageFlow</param>
        public void RedirectToExternalAccess(IPageFlow pageFlow)
        {
            RedirectToExternalAccess(pageFlow, null);
        }

        /// <summary>
        /// Redirects "back" to a pageflow from which was redirected. If RedirectToExternalPageFlow(IPageFlow,IPageFlow,Dictionary) was
        /// used to redirect to the given pageFlow this is a method to use to redirect back to that pageflow.
        /// </summary>
        /// <param name="pageFlow">The pageflow to which was redirected using RedirectToExternalPageFlow</param>
        /// <param name="externalUserData">The userdata to give back to the pageflow from which was redirected</param>
        public void RedirectToExternalAccess(IPageFlow pageFlow,
                                                    Dictionary<string, object> externalUserData)
        {
            if (!IsRunning(pageFlow) || !pageFlow.UserData.Contains(UDKEY_EXTERNAL_PAGE_FLOW))
            {
                return;
            }

            Guid id = (Guid)pageFlow.UserData[UDKEY_EXTERNAL_PAGE_FLOW];
            pageFlow.UserData.Remove(UDKEY_EXTERNAL_PAGE_FLOW);
            IPageFlow externalPageFlow = PageFlowDirectory.Provider.GetPageFlow(id);

            if (externalPageFlow == null)
            {
                return;
            }
            if (externalPageFlow.Status == PageFlowStatus.Suspended)
            {
                externalPageFlow.Resume();
            }
            if (externalUserData != null)
            {
                foreach (KeyValuePair<string, object> keyValuePair in externalUserData)
                {
                    externalPageFlow.UserData[keyValuePair.Key] = keyValuePair.Value;
                }
            }
            externalPageFlow.RedirectToCurrentPage();
        }
    }
}
